<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>bind 自实现以及柯里化curry 自实现带缓存</title>
</head>

<body>

</body>
<script>
  // bind 自己实现
  function myBind(target){
    var _this = this;
    var args = [...arguments].slice(1);
    return function(){
        args = args.concat([...arguments]);
       _this.apply(target,args); 
    }
  }
  Function.prototype.myBind = myBind;
  var obj1 = {
    name:'小红',
   
    say:function(age){
      console.log('我是'+ this.name,'年龄：'+ age);
    }
  }
  var obj2 = {
    name:'小蓝',
    
  }
  obj1.say(10)
  obj1.say.myBind(obj2)(11)
  // 兼容处理 多参数，可以分批传入直到原函数指定的长度执行
  // obj1.say.call(obj2,13)

  function myCall(target){
    var _this = this; 
    var fnName  = _this.name;  // 函数的名字
    var args = [...arguments].slice(1);
    target[fnName] = _this;
    var result = target[fnName](...args);
    delete target[fnName];
    return result;     
  }
  Function.prototype.myCall = myCall;
  // obj1.say.myCall(obj2,15);
  // apply

  /* function myApply(target){
    var _this = this; 
    var fnName  = _this.name;  
    var args = [...arguments].slice(1)[0]; 
    target[fnName] = _this;
    var result = target[fnName](...args);
    delete target[fnName];
    return result;     
  } 
  Function.prototype.myApply = myApply;
  obj1.say.myApply(obj2,[18]); */
  
  // 展开运算符是es6 新语法，考虑到版本，用普通for 循环代替
  function myApply(target){
    var _this = this; // 方法体
    var fnName  = _this.name;  // 函数的名字

    var args = [];
    var argsLength = arguments[1].length;
    for(var i = 1;i<argsLength; i++ ) {
      args.push(arguments[i]);
    }   
    target[fnName] = _this;
    var strExpression = 'target[fnName](' + args.toString() + ')';
    // var result = target[fnName](...args);

    var result = eval(strExpression);
    delete target[fnName];
    return result;     
  }
  Function.prototype.myApply = myApply;
  obj1.say.myApply(obj2,[18,19]);

  // 函数体内部用arguments获得实参列表，而functionName.length 可以获取形参个数，通过这个可以控制要传入参数个数

  // curry 柯里化函数， 目标f(a,b,c) 转化成f(a)(b)(c) 或者 f(a,b)(c) f(a(b,c)
  // 直到指定的参数才执行，返回最终值
  // 直接es6 啊，快速省事

  function test(a,b,c){
    var sum = a + b + c;
    console.log('和为='+ sum);
    return sum;
  }
  function curry(f){
    var fLength = f.length; // 形参个数
    var args = [...arguments];
    var lastArgsNum =  args.length - 1;
    var lastArg = args.slice(1); // 上一次的实际参数
    var cached = curry.cached ? curry.cached : curry.cached = {};
    // 第一次就全部传入所需的参数
    if(fLength <= lastArgsNum){ 
      
      var key = lastArg.toString().trim();
      if(cached[key]){
        return  cached[key]
      } 
      return cached[key] = f( ...lastArg );
    } 
    // fLength > lastArgsNum 的情况 
    return function(){
       var nextArgs = [...arguments];
       var total = lastArg.concat(nextArgs)
       return curry(f, ...total)
    }
  }


  // curry(test,1,2,3); 做缓存f,相同参数输入f, 只执行一次
  // curry(test,1)(2,3);
  // curry(test)(1)(2,3);
  // curry(test)(1)(2)(3);
  // curry(test)(3)(2)(1);

  // 老师写的代码实现,对比自己的版本
  // 对比发现老师的版本不支持第一次就调用的情况，或者说第一次就传入参数的情况，比我的版本运用性更差一点
  // 但是api 的使用更加简明，比如比如指定第一次只能传函数，思考的更简单，也更规范一点，有利有弊
  function curry1(func){
    var funcLength = func.length;
    var curried = (...args) =>{
        if(args.length < funcLength){
          return (...rest) =>curried(...args, ...rest);
        }
        return func(...args)
    }
    return curried;
  }
  curry1(test,1)(2,3); // 不支持这种写法，而我的实现是是支持这种写法的
  curry1(test)(1)(2,3);

</script>

</html>